/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.xeerkat.agent.tools;

import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import org.infoset.xml.Document;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.Element;
import org.infoset.xml.Name;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.restlet.util.Resolver;
import org.xeerkat.agent.services.ServiceRegistry;
import org.xeerkat.xmpp.ID;

/**
 *
 * @author alex
 */
public class InstallProcess implements Runnable {
   
   abstract class Operation {
      abstract void apply(Element e)
         throws Exception;
   };
   class AddServiceOperation extends Operation {
      void apply(Element addOp) 
         throws Exception
      {
         try {
            Iterator<Element> services = addOp.getElementsByName(ServiceRegistry.SERVICE);
            while (services.hasNext()) {
               agentManager.addService(to,from,services.next());
            }
         } catch (Exception ex) {
            agentManager.getLogger().log(Level.SEVERE,"Cannot install service.",ex);
            throw new Exception("Cannot install service due to exception.");
         }
      }
   }
   class ReplaceServiceOperation extends Operation {
      void apply(Element addOp) 
         throws Exception
      {
         try {
            Iterator<Element> services = addOp.getElementsByName(ServiceRegistry.SERVICE);
            while (services.hasNext()) {
               agentManager.replaceService(to,from,services.next());
            }
         } catch (Exception ex) {
            agentManager.getLogger().log(Level.SEVERE,"Cannot install service.",ex);
            throw new Exception("Cannot install service due to exception.");
         }
      }
   }
   class RemoveServiceOperation extends Operation {
      void apply(Element removeOp) 
         throws Exception
      {
         String path = removeOp.getAttributeValue("path");
         String qname = removeOp.getAttributeValue("name");
         String iface = removeOp.getAttributeValue("interface");
         try {
            Name serviceName = removeOp.getNamespaceScope().expandName(qname, false);
            agentManager.removeService(to,from,serviceName,path,iface==null ? null : new URI(iface));
         } catch (Exception ex) {
            agentManager.getLogger().log(Level.SEVERE,"Cannot remove service "+qname+", interface "+iface,ex);
            throw new Exception("Cannot remove service due to exception.");
         }
      }
   }
   class CopyResourceOperation extends Operation {
      void apply(Element copy) 
         throws Exception
      {
         String resource = copy.getAttributeValue("resource");
         String from = copy.getAttributeValue("from");
         String target = copy.getAttributeValue("target");
         if (resource==null && from==null) {
            throw new Exception("One of the 'from' or 'resource' attributes must be specified on "+AgentManager.COPY);
         }
         if (target==null) {
            throw new Exception("The 'target' attribute must be specified on "+AgentManager.COPY);
         }
         target = agentManager.expand(target);
         URL loc = null;
         if (resource!=null) {
            loc = cloader.getResource(resource);
            if (loc==null) {
               throw new IOException("Unable to locate resource "+resource);
            }
            agentManager.getLogger().info("Copying: (Resource) "+resource+" -> "+target);
         } else {
            from = agentManager.expand(from);
            agentManager.getLogger().info("Copying: "+from+" -> "+target);
            try {
               loc = (new File(from)).toURL();
            } catch (IOException ex) {
               agentManager.getLogger().log(Level.SEVERE,"Cannot generate URL from file "+from,ex);
               throw new Exception("Cannot location resource for copy.");
            }
         }
         File targetFile = new File(target);
         targetFile.getAbsoluteFile().getParentFile().mkdirs();
         copy(loc,targetFile);
      }
   }
   class DeleteOperation extends Operation {
      void apply(Element copy) 
         throws Exception
      {
         String file = copy.getAttributeValue("target");
         if (file==null) {
            throw new Exception("The 'target' attribute must be specified on "+AgentManager.DELETE);
         }
         file = agentManager.expand(file);
         final List<File> queue = new ArrayList<File>();
         queue.add(new File(file));
         
         boolean ok = true;
         int mark = -1;
         while (ok && queue.size()>0) {
            File target = queue.remove(queue.size()-1);
            if (target.isDirectory()) {
               if (mark==queue.size()) {
                  agentManager.getLogger().info("Deleting: "+target.getAbsolutePath());
                  ok = target.delete();
                  mark = -1;
               } else {
                  mark = queue.size();
                  queue.add(target);
                  target.listFiles(new FileFilter() {
                     public boolean accept(File f)
                     {
                        queue.add(f);
                        return false;
                     }
                  });
               }
            } else {
               agentManager.getLogger().info("Deleting: "+target.getAbsolutePath());
               ok = target.delete();
            }
         }
         
         if (!ok) {
            agentManager.getLogger().info("Not all files could be deleted.");
         }

      }
   }
   ID to;
   ID from;
   ClassLoader cloader;
   Resolver<String> environment;
   AgentManager agentManager;
   
   public InstallProcess(ID to,ID from,AgentManager manager,URL location,Resolver<String> environment)
   {
      this.to = to;
      this.from = from;
      URL [] urls = new URL[1];
      urls[0] = location;
      this.cloader = new URLClassLoader(urls,this.getClass().getClassLoader());
      this.environment = environment;
      this.agentManager = manager;
   }
   public void run() {
      URL installLoc = cloader.getResource("install.xml");
      DocumentLoader docLoader = new SAXDocumentLoader();
      Map<Name,Operation> operations = new TreeMap<Name,Operation>();
      operations.put(AgentManager.COPY,new CopyResourceOperation());
      operations.put(AgentManager.DELETE,new DeleteOperation());
      operations.put(AgentManager.ADD_SERVICE,new AddServiceOperation());
      operations.put(AgentManager.REPLACE_SERVICE,new ReplaceServiceOperation());
      operations.put(AgentManager.REMOVE_SERVICE,new RemoveServiceOperation());
      try {
         Document installDoc = docLoader.load(installLoc.toURI());
         Iterator<Element> elements = installDoc.getDocumentElement().getElementChildren();
         while (elements.hasNext()) {
            Element child = (Element)elements.next();
            Operation op = operations.get(child.getName());
            op.apply(child);
         }
      } catch (Exception ex) {
         agentManager.getLogger().log(Level.SEVERE,"Cannot install upgrade due to exception.",ex);
      }
   }
   
   protected void copy(URL location,File target)
      throws IOException
   {
      InputStream is = location.openStream();
      FileOutputStream os = new FileOutputStream(target);
      byte [] buffer = new byte[16384];
      int len;
      while ((len=is.read(buffer))>=0) {
         os.write(buffer,0,len);
      }
      is.close();
      os.close();
   }
}
